package com.web_bloks.storage.client;

import java.util.Vector;

import com.google.gwt.gears.client.Factory;
import com.google.gwt.gears.client.database.Database;
import com.google.gwt.gears.client.database.DatabaseException;
import com.google.gwt.gears.client.database.ResultSet;
import com.web_bloks.system.client.Logger;

/**
 * Provides a 'template' for Gears LocalServer.
 * Don't forget to call 'initialize' after ctor...
 * 
 * @author Jean-Lou Dupont
 * @pattern Template Method
 */
abstract public class GearsLocalStorageTemplate {

	public final static int DEFAULT_TTL = 24*60*60*1000;
	public final static long DEFAULT_CACHE_TIMEOUT = 24*60*60*1000; // 1day in milli

	/**
	 * Database instance
	 */
	Database db = null;

	/**
	 * Constructor
	 */
	public GearsLocalStorageTemplate() {
		super();
	}
	
	/**
	 * Used by derived classes to set the database schema
	 * @return String
	 */
	public abstract String getSchema();

	/**
	 * The equivalent of the database name
	 * 
	 * @return String
	 */
	public abstract String getStorageName();
	
	/**
	 * Used by derived classes to set the database table name
	 * @return String
	 */
	public abstract String getTableName();
	
	
	// ===================================================
	
	/**
	 * Put method
	 * e.g. propertyKeyList:  prop1, prop2
	 * 
	 * 
	 * @throws LocalStorageException
	 */
	public void put(String propertyKeyList, String[] propertyValueList) 
		throws LocalStorageException {
		
		if ( null == propertyKeyList || propertyKeyList.length()==0 ) {
			throw new LocalStorageException( this.getClass() + ".put, storage:["+getStorageName()+"]"+" propertyKeyList cannot be null or empty" );
		}
		if ( null == propertyValueList || propertyValueList.length==0 ) {
			throw new LocalStorageException( this.getClass() + ".put, storage:["+getStorageName()+"]"+" propertyValueList cannot be null or empty" );
		}

		ResultSet result = null;
		int lastrow = 0;
		
		StringBuffer valuesStatement = new StringBuffer();
		valuesStatement.append("VALUES(");
		
		boolean start = true;
		for (int i=0;i<propertyValueList.length;i++) {
			if (!start)
				valuesStatement.append(",");
			valuesStatement.append("?");
			start = false;
		}
		valuesStatement.append(")");
		
		try {
			result = this.db.execute(
					"INSERT OR REPLACE INTO "+getTableName()+" ("+propertyKeyList+") "+valuesStatement, 
					propertyValueList );
			lastrow = this.db.getLastInsertRowId();
			
			Logger.logDebug(this.getClass()+".put: result count: " + result.getFieldCount() +", lastrowid:" + lastrow);
			
		} catch(DatabaseException e) {
			Logger.logError(this.getClass()+".put, storage:["+getStorageName()+"]"+".put: EXCEPTION raised, msg="+e.getMessage());
			throw new LocalStorageException( e.getMessage() );
		}
		
		Logger.logDebug(this.getClass()+".put, storage:["+getStorageName()+"]"+".put: stored with lastrow["+Integer.toString(lastrow)+"]");
	}

	/**
	 * Get
	 * NOTE: don't forget to add 'WHERE' when necessary...
	 * 
	 * @param String selectStatement e.g. "fieldA, fieldB"
	 * @param int selectCount the number of field(s)
	 * @param String whereStatement e.g. fieldA=?, fieldB=?
	 * @param String[] wherePropertyList the list of where properties
	 * @return Vector<String[]> resultSet list
	 * 
	 * @throws LocalStorageException
	 */
	public Vector< String[] > get(String selectStatement, int selectCount, String whereStatement, String[] wherePropertyList) 
		throws LocalStorageException {
		
		if ( (null == selectStatement) || ( selectStatement.length() == 0 ) )
			throw new LocalStorageException( this.getClass()+".get storage:["+getStorageName()+"]"+": select statement cannot be null/empty" );

		if ( (null == whereStatement) || ( whereStatement.length() == 0 ) )
			throw new LocalStorageException( this.getClass()+".get storage:["+getStorageName()+"]"+": where statement cannot be null/empty" );
	
		Vector<String[]> liste = new Vector<String[]>();
		
		boolean found = false;

		String statement = "SELECT "+selectStatement+" FROM "+getTableName()+" "+whereStatement; 
		Logger.logDebug(this.getClass()+".get, storage["+getStorageName()+"]"+": statement["+statement+"]");
		
		ResultSet result = null;
		try {
			if (null!=wherePropertyList)
				result = this.db.execute( statement, wherePropertyList );
			else
				result = this.db.execute( statement );
			
			while( result.isValidRow() ) {
				found=true;

				String[] resultSet = new String[ selectCount ];			
				
				for (int index = 0;index<selectCount;index++) {
					resultSet[index] = new String( result.getFieldAsString(index));
				}
				
				liste.add( resultSet );
				
				result.next();
			}

			result.close();
			
		} catch(DatabaseException e) {
			//Logger.logError(this.getClass()+".get storage:["+storageName+"]"+": DatabaseException raised whilst SELECTing. Msg= " +e.getMessage());
			throw new LocalStorageException( e.getMessage() );
		} catch( Exception e ) {
			//Logger.logError(this.getClass()+".get, storage:["+storageName+"]"+": Exception raised whilst SELECTing. Msg= " +e.getMessage());
			throw new LocalStorageException( e.getMessage() );
		}

		if (!found) {
			Logger.logInfo(this.getClass()+".get, storage["+getStorageName()+"]"+" not found");
			return null;
		}
				
		//Logger.log(thisClass+" storage:["+storageName+"]"+".get: got name["+name+"]");
		Logger.logDebug(this.getClass()+".get, storage["+getStorageName()+"]"+" FOUND");
		
		return liste;
	}
	/**
	 * Delete a document
	 * @param
	 */
	public void delete(String nsname, String name) 
		throws LocalStorageException {
		
		Logger.logDebug(this.getClass()+".delete, storage:["+getStorageName()+"]");
		
		if ( (null == nsname) || ( nsname.length() == 0 ) )
			throw new LocalStorageException( this.getClass()+".delete storage:["+getStorageName()+"]"+": nsname cannot be null/empty" );

		if ( (null == name) || ( name.length() == 0 ) )
			throw new LocalStorageException( this.getClass()+".delete storage:["+getStorageName()+"]"+": name cannot be null/empty" );
		
		try {
			this.db.execute(	"DELETE FROM simpleobjectstore WHERE nsname=? AND name=?", new String[] {nsname,name} );
		} catch(DatabaseException e) {
			throw new LocalStorageException( e.getMessage() );
		} 
	}

	
	/**
	 * Clears the entire tab;e
	 */
	public void clear() 
		throws LocalStorageException {
		
		Logger.logWarn( getClass()+".clear" );
		
		try {
			this.db.execute("DELETE FROM " + getTableName() );
		} catch(DatabaseException e) {
			throw new LocalStorageException( e.getMessage() );
		}
	}
	/**
	 * Initialize the database OR creates a new one if not already present.
	 * 
	 * @return false if already initialized
	 * @throws LocalStorageException
	 */
	public boolean initialize() 
		throws LocalStorageException {
		
		if (!isGearsInstalled())
			return false;
		
		Logger.logDebug( this.getClass()+".initialize" );
		
		if (this.db != null)
			return false;
		
		if (null == getStorageName() || getStorageName().length() == 0) {
			throw new LocalStorageException("storage name cannot be null");
		}		
		
		if (null == getTableName() || getTableName().length() == 0) {
			throw new LocalStorageException("table name cannot be null");
		}
		
		createDatabase();
		
		return true;
	}

	protected void createDatabase() 
		throws LocalStorageException {
		
		Logger.logDebug( this.getClass()+".createDatabase: storage name["+getStorageName()+"] table name["+getTableName()+"]" );
		
		Factory factory = Factory.getInstance();
		this.db = factory.createDatabase();
		this.db.open( getStorageName() );
		try {
			
			this.db.execute("create table if not exists "+getTableName()+" "+ getSchema() );
			
		} catch(DatabaseException e) {
			Logger.logError(this.getClass()+".createDatabase, storage:["+getStorageName()+"]"+":exception raised whilst creating table. Msg= " + e.getMessage());
			try {
				Logger.logError(this.getClass()+".createDatabase, storage:["+getStorageName()+"]"+":dropping table. Msg= " + e.getMessage());
				this.db.execute("drop table " + getStorageName() );
			} catch(Exception e2) {
				Logger.logError(this.getClass()+".createDatabase, storage:["+getStorageName()+"]"+":dropping table raised exception. Msg= " + e2.getMessage());
			}
			throw new LocalStorageException( e.getMessage() );
		}		
	}
	
	/**
	 * True if Gears is available
	 * @return
	 */
	public boolean exists() {
		return isGearsInstalled();
	}

	public boolean isPersistent() {
		return true;
	}

	
	/*===================================================================
	 * PRIVATE 
	 ===================================================================*/
	private static native boolean isGearsInstalled() /*-{
		try {
			return $wnd.google.gears.factory != null;
		} catch (e) {
			return false;
		}
	}-*/;
	

}//END